Skip to main content

Visitor Management System

Introduction

With the move to Level 7 at the refurbished Petone Office, a new Visitor Management System was required given the need to support a reception capability.

The primary purpose of the system is to record access to the site by visitors and guests so that in the event of an emergency, the system would be able to determine who had entered and departed the site that day.

This document provides the detailed configuration and decisions to support the installation and deployment of the service.

Design Overview

The following diagram provides and overview of the solution, and the components.

:::DECISION Selection of SaaS Service

After a selection process, the selection of the Sign In App software as a service was chosen. :::

img-Diagram_2

The following section will dive into the configuration deployed within the software to configure it for the Visitor Management System.

Sites

In this section, we will configure the Sites element which provides the majority of customisation for the service.

Details

The following table describes the key aspects of the site.

SettingValueNotes
General
Site NamePetone
Site AddressLevel 7
25 Victoria Street,
Petone
Wellington
Time ZoneUTC +13:00 - Pacific/Auckland (12:48 NZDT)
LanguageUTC +13:00 - Pacific/Auckland (12:48 NZDT)
Reply to NameVisitor Management
Reply to Emailvisitor@securitease.com
Mobile Sign In
img-Pasted_image_20241217102935
Contactless
MessageThank you for choosing Contactless Signin.Introduction message displayed to visitors signing in using contactless.

Group Visibility

Groups provide containers for configuration that can be used to alter the user experience for different use cases. We are deploying only three groups.

SettingValueNotes
VisibilityAll groups are shown.By default the site will show all groups. Here you can change the groups visible to the site.

Branding

Custom branding has been deployed to the reception iPad that is used. This can be altered, but for the initial deployment, this will be the look and feel.

SettingValueNotes
img-Pasted_image_20241217103231

Custom Fields

Custom fields can be used to collect extra information from your visitors. They can be set to appear on sign in or sign out.

SettingApplied ToTypeNotes
Sign In Fields
CompanyVisitorsTest
VisitingVisitorsNotify List
Car RegistrationVisitorsTextThis is used to identify visitor vehicles and prevent clamping.
ReasonVisitorsTextUsed by the Unifi Application for visit reason.
Number of PackagesDeliveriesNumber
Package RecipientDeliveriesNotify List
Sign Out Fields
RetrievedDeliveriesSignature

Messages

Messages can be used to display important information to visitors and staff when they sign in.

SettingValueNotes
TitleHealth & Safety
ContentYOUR PASS MUST BE WORN AT ALL TIMES. PLEASE RETURN YOUR PASS AND SIGN OUT UPON LEAVING

All visitors are subject to the Company's Health & Safety regulations. In case of fire/emergency please report to the evacuation points for a roll call. Smoking in designated areas only.
Attach a fileN/A
ActionCheckbox that must be ticked (I agree)
Checkbox labelI have read and understood the above
Redisplay MessageWhen message is updated
Email Copy to VisitorDisabled
GroupsVisitors

Evacuation Points

Evacuation points are site locations you can assign to groups or repeat visitors.

SettingValueNotes
TitleEvacuation Point
IconEAn evacuation point icon can be a custom letter/number
img-Pasted_image_20241217125919
img-Pasted_image_20241217130012
DefaultsVisitors / Employees / StaffYou can set the default evacuation point for each of this site's groups below. These can be overwritten for repeat visitors by editing a group member's settings.

Privacy

Control how visitor information is displayed and how it can be interacted with.

SettingValueNotes
Show autocomplete for returning visitorsSet
Only show pre-registered names with an exact matchSet
Hide visitors on the sign out listNot SetDecision on this in the register.
Hide the Sign In App logo on the thank you screenSet

Badges

Here you can select which badge template is used when printing visitor badges.

SettingValueNotes
img-Pasted_image_20241217130348
Type54mm - Standard With Photo

Features

Control which features are available for guests to utilise when signing in or out.

SettingValueNotes
QR Code ScanningEnabled
Visitor PhotosEnabled
SpacesNot Licensed
On Site ReportsNot Enabled

Devices

Connect and manage which devices are linked to your Sign In App subscription.

SettingValueNotes
Device NamePetone-Reception
Send Status AlertsNo

Sign In Points

Sign In Points can be used to display static QR code on premises so visitors can register their presence without touching a device and a record of the visitor can be maintained.

SettingValueNotes
NameReception Sign in
MessageYou are at reception.
GroupsVisitors, Deliveries
Hide Notify ListEnabled
QR CodeCan be downloaded
PDFCan be downloaded

Groups

Manage groups of visitors. Set up new returning visitors and show or hide groups from the app.

SettingValueNotes
VisitorsStandardStandard group for visitors.
EmployeesIntegrationUses Entra GraphAPI enabled Webapp to retrieve staff details.
DeliveriesDeliveryUsed for delivery / couriers.

Visitors

Details

SettingValueNotes
Group NameVisitors

Sign In Options

The following settings allow you to customise the sign in process that your visitors take. Changing options such as the welcome message or whether you wish for the iPad app to take a photo allows you to extend a tailor made greeting to your visitors.

SettingValueNotes
Welcome MessageWe have contacted the staff member, and they will be with you shortly.
Print Visitor BadgeEnabled
Safety check settingsReject sign in when entry criteria is not metSafety check ensure the people signing into this group meet your site entry criteria. When enabled, this option will check both your Block list and site Health certificate requirements during the sign in process.

Sign Out Options

The following settings allow you to customise the sign out process that your visitors take. Here you can adjust sign out automation to handle visitors who forget to sign out upon leaving.

SettingValueNotes
Sign Out MessageThank you for visiting SecuritEase.
AutomationAutomatically clear group members in or out statusYou can automate when group members are signed out of this group. This can help manage any visitors who did not sign out when they left.
Time00:00

Badges

Here you can override the site default badge and select a unique badge to be printed when a visitor signs in to this group.

SettingValueNotes
Site DefaultEnabled

Pre-Registrations

Here you can adjust how pre-registrations work for this group, including customisation of the pre-registration email sent to pre-registered guests.

SettingValueNotes
Preregistration email subjectNot Set
Email MessageNot Set
Email AttachmentNot Set
Include Contactless LinksEnabled
Include QR CodeEnabled
Require Pre RegistrationNot Enabled
Pre Registration ApprovalsNot Enabled

Data and Privacy

Group privacy options allow you to fully control how group member data is displayed or stored across Sign In App.

SettingValueNotes
Keep Visitor Datafor 1 dayThis option will permanently delete all visitor data older than your selection, so use with care. Visitor data will be removed from our internal backups within 2 weeks after deletion.
Keep Visitor PhotosAligned to Visitor Data retention
Keep Visitor BadgesEnabled
Store visitors sign in and out locationsEnabled
VisibilityThe following options allow control of how and where the group and its members appear within Sign In App.
Show group on the Evacuation ListEnabledThis should be enabled to allow visitors to show up on the list
Show group on the iPad AppEnabled

Employees

Members

SettingValueNotes
Entra DirectoryGroup Synced

Details

SettingValueNotes
Group NamePetone Staff

Personal Fields

Personal fields allow you to collect additional information from your repeat group members. This information will be stored with their member details indefinitely unlike custom fields. There is a limit of 15 personal fields per group.

reorder

SettingValueNotes
Field LabelEmail
Show this field on evacuation listNot Enabled
Show this field on host notificationsNot Enabled
Allow this field to be seen by the group memberEnabled
Field LabelPhone Number
Show this field on evacuation listNot Enabled
Show this field on host notificationsNot Enabled
Allow this field to be seen by the group memberEnabled
Field LabelRole
Show this field on evacuation listEnabled
Show this field on host notificationsNot Enabled
Allow this field to be seen by the group memberEnabled

Sign In Options

The following settings allow you to customise the sign in process that your visitors take. Changing options such as the welcome message or whether you wish for the iPad app to take a photo allows you to extend a tailor made greeting to your visitors.

:::NOTE This option is not enabled

This element applies to sign in of staff using the system. :::

SettingValueNotes
Welcome MessageN/A
Chance of Photo when a group member logs in0%
Verify IdentityNot Set
Visitor badge optionsNot Set
Safety CheckDon't check

Sign Out Options

The following settings allow you to customise the sign out process that your visitors take. Here you can adjust sign out automation to handle visitors who forget to sign out upon leaving.

SettingValueNotes
Not Set

Badges

Here you can override the site default badge and select a unique badge to be printed when a visitor signs in to this group.

SettingValueNotes
Default used

Notifications

Here you can adjust how group members receive different visitor notifications.

SettingValueNotes
Notify HostSet
Email
Sign OutNo notification.

Companion App

Here you can adjust the companion app settings for this group. The companion app allows a group member to use their smartphone to access their QR code, mobile sign in, pre-register visitors, and view and export the evacuation list.

:::NOTE Not Used

This function is not enabled.

Onboarding

re you can adjust the onboarding settings for this group, including customising the welcome email group members receive. :::

:::NOTE Not Used

This function is not enabled.

Data and Privacy

:::

:::NOTE Not Used

This function is not enabled. :::

Deliveries

Details

SettingValueNotes
Group NameDeliveries

Sign In Options

The following settings allow you to customise the sign in process that your visitors take. Changing options such as the welcome message or whether you wish for the iPad app to take a photo allows you to extend a tailor made greeting to your visitors.

SettingValueNotes
Welcome MessageNot Set
Print Visitor BadgeNot Set
Scan Delivery LabelEnabled
Safety CheckDon't checkSafety check ensure the people signing into this group meet your site entry criteria. When enabled, this option will check both your Block list and site Health certificate requirements during the sign in process.

Sign Out Options

The following settings allow you to customise the sign out process that your visitors take. Here you can adjust sign out automation to handle visitors who forget to sign out upon leaving.

SettingValueNotes
MessageNot set
AutomationYou can automate when group members are signed out of this group. This can help manage any visitors who did not sign out when they left.
Clear Group MembersNever

Badges

Here you can override the site default badge and select a unique badge to be printed when a visitor signs in to this group.

SettingValueNotes
Default used

Data and Privacy

Group privacy options allow you to fully control how group member data is displayed or stored across Sign In App.

SettingValueNotes
Keep Visitor Datafor 7 daysThis option will permanently delete all visitor data older than your selection, so use with care. Visitor data will be removed from our internal backups within 2 weeks after deletion.
Keep Visitor PhotosAligned to Visitor Data retention
Keep Visitor BadgesEnabled
Store visitors sign in and out locationsEnabled
VisibilityThe following options allow control of how and where the group and its members appear within Sign In App.
Show group on the Evacuation ListEnabledThis should be enabled to allow visitors to show up on the list
Show group on the iPad AppEnabled

Events

:::NOTE Decision

This is not used at present. :::

Account Settings

Account Info

Here you can amend the client space information such as your company name and logo.

SettingValueNotes
Company NameSecuritEase
img-SELogo

SMS Tokens

Sign In App will send notifications when visitors arrive by email or text message (SMS). To receive SMS notifications, your account must have tokens available.

SettingValueNotes
Not used

Portal Users

Here you can add or edit portal users that have access to this client space. Adjust their access permissions and select who is your primary user or technical contact.

SettingValueNotes
NameSupport
RoleAdministrator
Site AccessAll
Tech ContactTick
2FAEnabled

Roles and Permissions

Here you can set up different user roles and the permissions associated with those roles.

SettingAdminStandardNotes
Audit logxCan view a full audit log of who made what changes when
View today pagexxCan view the today page
View visitor locationsxCan see the geo-location of sign-ins and sign-outs of visitors
Sign in/out visitorsxxCan sign in/out repeat and standard visitors
Approve Pre-registrationsxCan approve pre-registered users
Sign out allxxCan sign out all group visitors at once
View evacuation listxxCan view the evacuation list for assigned sites
View evacuation reportsxxCan view evacuation reports

Password Policy

This is where you can change the password policy for users logging in.

SettingValueNotes
Password Never ExpireNever
Force Reauthentication30 days
Force 2 FactorSet
Prevent recently used passwordsSet

Audit Log

SettingValueNotes
EnabledYes

Feature Management

Customise your Sign In App experience even further.

SettingValueNotes
Shared EvacuationsEnabled
DeliveriesEnabled
ContactlessEnabled
Remote SiteNot Enabled
Pre-Reg FormsNot Enabled
NotificationsEnabledVisitor Sign In
Visitor Sign Out
ClientAPIEnabledAPI Key created. Used in API integrations.
Safety CheckEnabled

Notifications

Notifications allow for Webhooks to be triggered for certain events.

There are two webhooks enabled:

  1. Visitor Sign In
  2. Visitor Sign Out

Visitor Sign In

SettingValueNotes
EnabledTrue
NameVisitorWebhook-SignIn
SitePetone
TriggerSign In
GroupVisitors
ChannelWebhook
Notification Settings
Webhook URLhttps://signinwebhook.thoughtlabs.workers.dev/
Secret KeyDisplayed in GUI

Visitor Sign Out

SettingValueNotes
EnabledTrue
NameVisitorWebhook-SignOut
SitePetone
TriggerSign Out
GroupVisitors
ChannelWebhook
Notification Settings
Webhook URLhttps://signinwebhook.thoughtlabs.workers.dev/
Secret KeyDisplayed in GUI

Safety Check

Each time a visitor signs in to a group with the Access Control feature enabled, your Block List will be checked to ensure they are allowed on to site. If the name appears on your Block List, that person will either be denied access or allowed on to site depending on the group settings.

SettingValueNotes
Health CertificatesNot Used
Block ListNot Used

Evacuations

SettingValueNotes

Testing and Validation

Test Plans will be documented in the section 2 - Testing.

Appendices

Decision Table

The following is a list of key decisions and ownership.

DecisionDescriptionRationaleOwner
Use Microsoft Entra for staff identity detailsCompany standard for staff identity.James Winskill

Controls Table

The following table describes the key control features of the service, and the rationale behind their use.

ControlDescriptionRationale
Separation of NetworksAll networks are segmented and have no associated direct routing.  All traffic is isolated to vLANs and Access control lists are used to inspect traffic.Reduced surface area for attack.

Good practice

Admins Table

NameEmailRolePermissionsNotes
Tim Jacksontim.jackson@securitease.comSuper AdminSuper AdminInitial Admin until signoff
James Winskilljames.winskill@securitease.comSuper AdminSuper Admin
Chris McKenziechris.mckenzie@securitease.comSuper AdminSuper Admin

Constraints Table

The following table provides a list of known constraints that have presented themselves during the initial build of the service.

ConstraintDescriptionTreatmentOwner
Entra Sync - PushMicrosoft Entra can only sync changes such as new users.  It currently does not de-provision or amend groups to existing users and cannot be used for portal access control.Manual process to true up.James Winskill
Cloud BackupsThe system produces automated cloud backups.Determine this is an acceptable place for system level backup data.

Data Controls

The following table is in support of the Data Controls and Classification section in this document.

:::NOTE Information

This section is to advise on possible inclusion into SecuritEase’s risk management system. It is not a formal record of the control, nor the risk posed :::

ControlControl NameControl DescriptionControl OwnerNote
Encryption at RestData EncryptionRSA 2048 EncryptionSystem Owner
Encryption in TransitData EncryptionRSA 2048 SigningSystem Owner
RBACRBAC AccessRole Based Access Control implementedSystem OwnerRBAC used to control access to system and personnel data
Data SovereigntyLocation of at rest dataSignInApp - AWS Australia
iPad Petone Level 7
Entra ID - Azure Global
System Owner

iPad Settings

Data Classification

Classification Types

Based on the NZ Government Data Classification Policy.

LevelDescriptionNotes
Level 1Publicly Available InformationPublic information only
Level 2Restricted InformationRestricted information, PII, confidential business information and client data.
Level 3Sensitive InformationFinancial information, personal health data, legal or national security information.

Data Sources

SourceTypesSystemsClassification
Staff DataName
Email
Entra
Level 2
Visitor DataName (First + Last)
Company Name
Email
Visit Reason
Person Visiting
Car Registration (optional)
SignInAppLevel 2
DeliveriesRecipient
Date / Time
SignInAppLevel 1

Systems

NameDescriptionNotes
Microsoft EntraUsed to populate staff data into the system.  Controls group and rights assignments.

Only certain groups are synced so not all staff records exist in systems.
Office 365 EmailUsed to send notifications of visitors to staff.

It is used to send details to visitors (if used).

Incident Response Design

Overview

:::NOTE Information

Good practice in Incident Response is to have any events reported in a standard way, and assign them to case owners as part of any ongoing investigation. PagerDuty has been configured to provide alerting capability. :::

Business Service

Business services provide a way to model capabilities that span multiple technical services.

AreaSettingNotes
Service NamePetone Visitor Management
Service DescriptionVisitor Management system events from Petone L7 office.
Service OwnerJames Winskill
TeamGlobal Support Team

Supporting Service

A service in PagerDuty represents a component, microservice or piece of infrastructure a team operates, manages, and monitors.

AreaSettingNotes
Service NameAccess Control Response
Service DescriptionResponse Plan to incidents relating to systems, or escalations to physical access to SecuritEase buildings or monitored systems.
Service OwnerJames Winskill
Integrations
Event API
Slack Message
Workflow
Incident Workflow
Triage EventBased on severity Alert areas.
Assign SeverityBased on system categorisation of severity - map incident severity.
Assign PriorityAssign priority based on severity
Raise IncidentCreate an incident - start a response process.
Settings
Assign to Escalation Policy
Response Policy
Alert MappingCritical → High
Error → High
Warning → Low
Info → Low
Not Available → High
Retrigger Ack IncidentsNo
Auto ResolveNo
Responders and Stakeholders
Conference Bridge
Meeting URL
Response Play
Event Management
Orchestration Rule
Events create IncidentAll Events Monitored
All EventsSelect any event
Incident PrioritySet to Low
Add NoteSet to Low by Rule
Set SeveritySet to Warning
Remediate
Documentation Link
Link NameVisitor System - Response

Prototype Integration with Unifi Access

Overview

A prototype to integrate the VMS with the UniFi Access Manager was undertaken. This activity created a locally hosted API to

img-a92b6d7885ae33fc1580b9fcf2b20afb

Webhook Code

This code must be hosted locally as it talks to the Unifi Gateway locally.

:::NOTE Prototype Code

This code is intended as a guide to the prototype only. It was deployed as a microservice in ThoughtLabs Kubernetes environment. :::

// Constants
const SECRET = 'xxxxx';
const ACCESSURL = 'https://api.thoughtlabs.co.nz/api/v1/developer/visitors';
async function computeHmac(secret, message) {
const encoder = new TextEncoder();
const keyData = encoder.encode(secret);
const messageData = encoder.encode(message);
const cryptoKey = await crypto.subtle.importKey(

'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign(
'HMAC',
cryptoKey,
messageData
);
return Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}

function mapWebhookDataToSignInFormat(webhookData) {
const visitor = webhookData.visitor;
const visiting = webhookData.visiting;
const [firstName, ...lastNameParts] = visitor.name.split(' ');
const lastName = lastNameParts.join(' ');
// Convert ISO 8601 date to Unix timestamp
const startTime = Math.floor(new Date(webhookData.event_at).getTime() / 1000);
// Set end time to the end of the day (23:59:59)
const endDate = new Date(webhookData.event_at);
endDate.setHours(23, 59, 59, 999);
const endTime = Math.floor(endDate.getTime() / 1000);

return {
first_name: firstName,
last_name: lastName,
remarks: `Visiting: ${visiting.name}`,
mobile_phone: visiting.mobile || "",
email: visiting.email || "",
visitor_company: visitor.additional.Company || "",
start_time: startTime,
end_time: endTime,
visit_reason: visitor.additional.Reason,
resources: [
{
"id": "xxxx",
"name": "xxxxxxx",
"type": "door_group"
}
]
};
}
function mapWebhookDataToSignOutFormat(webhookData) {
// For sign-out, we only need to return the visitor's name
// as we'll use this to query for the visitor's ID in the API
const visitor = webhookData.visitor;
return {
name: visitor.name
};
}
async function handleSignIn(webhookData) {
const apiData = mapWebhookDataToSignInFormat(webhookData);
console.log('API Data for Sign In:', apiData);
const response = await fetch(ACCESSURL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
'Authorization': 'Bearer xxxx',
'CF-Access-Client-Id': 'xxxxx',
'CF-Access-Client-Secret': 'xxxxx'
},
body: JSON.stringify(apiData),
});

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response;
}
async function handleSignOut(webhookData) {
const apiData = mapWebhookDataToSignOutFormat(webhookData);

// Query the API to get visitors
const queryResponse = await fetch(`${ACCESSURL}`, {
method: 'GET',
headers: {
'Accept': 'application/json',
'Authorization': 'Bearer xxxxxx',
'CF-Access-Client-Id': 'xxxxx.access',
'CF-Access-Client-Secret': 'xxxxxx'
},
});

if (!queryResponse.ok) {
throw new Error(`HTTP error! status: ${queryResponse.status}`);
}
const responseData = await queryResponse.json();

// Assuming the API returns an object with a 'data' property containing the visitors
const visitors = responseData.data || [];

// Parse the webhook data for first name and last name
const [firstName, ...lastNameParts] = apiData.name.split(' ');
const lastName = lastNameParts.join(' ');

// Find the matching visitor
let matchingVisitor = null;
for (let visitor of visitors) {

if (visitor.first_name.toLowerCase() === firstName.toLowerCase() &&
visitor.last_name.toLowerCase() === lastName.toLowerCase()) {
matchingVisitor = visitor;
break;
}
}
if (!matchingVisitor) {
throw new Error('Visitor not found');
}
const visitorId = matchingVisitor.id;

// Now we can delete the visitor using the ID
const deleteResponse = await fetch(`${ACCESSURL}/${visitorId}`, {
method: 'DELETE',
headers: {
'Authorization': 'Bearer xxxxx',
'CF-Access-Client-Id': 'xxxxx.access',
'CF-Access-Client-Secret': 'xxxxxx'
},
});

if (!deleteResponse.ok) {
throw new Error(`HTTP error! status: ${deleteResponse.status}`);
}
return deleteResponse;
}
export default {
async fetch(request, env, ctx) {
if (request.method !== 'POST') {
return new Response('Method Not Allowed', { status: 405 });
}

const signatureHeader = request.headers.get('x-signinapp-webhook-signature');
if (!signatureHeader) {
return new Response('Missing signature header', { status: 400 });
}
const [timestamp, receivedSignature] = signatureHeader.split(',');
const t = timestamp.split('=')[1];
const s1 = receivedSignature.split('=')[1];
const body = await request.text();

// Compute HMAC
const expectedSignature = await computeHmac(SECRET, `${t}.${body}`);

if (s1 !== expectedSignature) {
return new Response('Invalid signature', { status: 401 });
}

try {
// Parse the webhook data
const webhookData = JSON.parse(body);

let response;
if (webhookData.event === 'visitor.sign-in') {
response = await handleSignIn(webhookData);
} else if (webhookData.event === 'visitor.sign-out') {
response = await handleSignOut(webhookData);
} else {
return new Response('Unsupported event type', { status: 400 });
}

return new Response(`Webhook processed: ${webhookData.event}`, { status: 200 });
} catch (error) {
console.error('Error processing webhook:', error);
return new Response('Error processing webhook', { status: 500 });
}
},
};

Visitor Dashboard

:::NOTE Optional Component

The following is an optional component that could be used to show who is currently visiting a site. The service has a hosted web server that obtains details from the API of current visitors, and their status. :::

The following prototype is hosted in a CloudFlare Workers environment, pointing to a test instance of the Application.

img-Pasted_image_20241217115137

img-Diagram_1

The code is as follows:

async function handleRequest(request) {
const API_ENDPOINT = 'https://backend.ap-se2.signinapp.com/client-api/v1/sites/3335/today'
const API_CREDENTIALS = 'xxxxxxx'
try {
const response = await fetch(API_ENDPOINT, {
headers: {
'Authorization': `Basic ${API_CREDENTIALS}`,
'Content-Type': 'application/json'
}
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
let thoughtLabsData = null;
for (let i = 0; i < data.length; i++) {
if (data[i].id === 7736) {
thoughtLabsData = data[i];
break;
}
}
let tableRows = '';
if (thoughtLabsData && Array.isArray(thoughtLabsData.visitors)) {
if (thoughtLabsData.visitors.length > 0) {
for (const visitor of thoughtLabsData.visitors) {
const statusIcon = visitor.status === 'signed_in'
? '<i class="fas fa-door-open signed-in status-icon" title="Signed In"></i>'
: '<i class="fas fa-door-closed signed-out status-icon" title="Signed Out"></i>';
// Format the datetime to show only time
let formattedInTime = 'N/A';
if (visitor.in_datetime) {
const date = new Date(visitor.in_datetime);
formattedInTime = date.toLocaleTimeString('en-NZ', {
hour: '2-digit',
minute: '2-digit',
hour12: false, // This will use 24-hour format
timeZone: 'Pacific/Auckland'
});
}
let formattedOutTime = 'N/A';
if (visitor.in_datetime) {
const date = new Date(visitor.in_datetime);
formattedOutTime = date.toLocaleTimeString('en-NZ', {
hour: '2-digit',
minute: '2-digit',
hour12: false, // This will use 24-hour format
timeZone: 'Pacific/Auckland'
});
}
tableRows += `
<tr>
<td>${visitor.name || 'N/A'}</td>
<td>${visitor.additional_fields.Company || 'N/A'}</td>
<td>${formattedInTime}</td>
<td>${visitor.additional_fields.Reason || 'N/A'}</td>
<td>
${visitor.photo_url ?
`<img src="${visitor.photo_url}" alt="${visitor.name}" style="width:50px; height:50px; object-fit:cover;">` :
'No photo available'}
</td>
<td>${visitor.additional_fields.Visiting || 'N/A'}</td>
<td>${statusIcon}</td>
</tr>
`;
}
} else {
tableRows = '<tr><td colspan="7">No Visitors currently checked in</td></tr>';
}
} else {
tableRows = '<tr><td colspan="7">Visitors data not found</td></tr>';
}
const NZDate = new Date().toLocaleString('en-NZ', {
timeZone: 'Pacific/Auckland',
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});

// Create an HTML response
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ThoughtLabs Visitors</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
body {
font-family: 'Arial', sans-serif;
margin: 0;
padding: 20px;
background-color: #f0f2f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
background-color: white;
border-radius: 8px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
padding: 20px;
position: relative;
}
.logo {
position: absolute;
top: 20px;
right: 20px;
width: 100px; /* Adjust as needed */
}
h1 {
color: #2c3e50;
margin-bottom: 10px;
}
.date {
color: #7f8c8d;
margin-bottom: 20px;
}
table {
border-collapse: separate;
border-spacing: 0 10px;
width: 100%;
}
th, td {
padding: 15px;
text-align: left;
}
th {
background-color: #3498db;
color: white;
font-weight: bold;
}
tr {
background-color: #ecf0f1;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
transition: all 0.3s ease;
}
tr:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
}
td:first-child, th:first-child {
border-top-left-radius: 8px;
border-bottom-left-radius: 8px;
}
td:last-child, th:last-child {
border-top-right-radius: 8px;
border-bottom-right-radius: 8px;
}
.mug-shot {
width: 50px;
height: 50px;
border-radius: 50%;
object-fit: cover;
}
.status-icon {
font-size: 24px;
}
.signed-in {
color: #2ecc71;
}
.signed-out {
color: #e74c3c;
}
</style>
</head>
<body>
<div class="container">
<img src="https://www.thoughtlabs.co.nz/images/TLLogo.png" alt="ThoughtLabs Logo" class="logo">
<h1>ThoughtLabs Visitors</h1>
<p class="date">Current Date: ${NZDate}</p>
<table>
<tr>
<th>Name</th>
<th>Company</th>
<th>Check-in Time</th>
<th>Reason</th>
<th>Mug Shot</th>
<th>Visiting</th>
<th>Status</th>
</tr>
${tableRows}
</table>
</div>
<script>

// Auto-refresh every 30 seconds
setInterval(function() {
location.reload();
}, 30000);
</script>
</body>
</html>
`;
return new Response(html, {
headers: { 'Content-Type': 'text/html' },
});
} catch (error) {
console.error('Error:', error);
return new Response('An error occurred while fetching employee data', { status: 500 });
}
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})

End of Document